Ahora que hemos pasado nuestro simple blog en PHP a Symfony2, empezamos a ver la potencia que nos ofrece este framework. Como último paso antes que podamos empezar a andar solos, vamos a mejorar algunos aspectos y añadir algunas funciones muy interesantes.
La consulta
Primero de todo, mejoramos la forma de obtener nuestros posts empleando el sistema de consultas avanzado de Doctrine:
Primero generamos una instancia del "QueryBuilder" mediante el "EntityManager". Esto nos permite emplear diversos métodos de Query Builder como select(), from() y addOderBy(). En este caso, queremos recuperar los posts en orden de creación descendente, para mostrar primero los más recientes.
Para referenciar al objeto que se quiere recuperar empleamos la notación corta "BloggerBlogBundle:Post", que sería equivalente a "Blogger\BlogBundle\Entity\Blog". Después de definir la consulta, se ejecuta la query mediante "getQuery()" que devuelve una instancia DQL de Doctrine, para después recuperar la colección de entidades Post mediante "getResult()". Las instancias DQL tienen otros métodos para devolver resultados como "getSingleResult()" y "getArrayResult()".
La vista
Ahora actualizamos la vista:
Vamos recorriendo la variable "posts" con Twig para luego ir mostrando los diferentes campos. Una cosa interesante es que ya que el contenido del post puede llegar a ocupar mucho espacio, se muestra una porción del mismo (en este caso 50 caracteres) y se permite acceder al post completo mediante el "Seguir leyendo...". Para que funcione, deberemos cambiar eso sí el método getBody de la clase Post de la siguiente manera:
Pero ahora mismo tenemos un problema, las queries se encuentran en el controlador, dentro de las acciones, con lo que no se pueden reutilizar. Veamos como solucionarlo.
Repositorios de Doctrine 2
Al crear la consulta de la acción "show" dentro de nuestro controlador ya estábamos haciendo uso de repositorios de doctrine, solo que en ese caso se empleaba la implementación por defecto de la clase "Doctrine\ORM\EntityRepository" para recuperar un Post de la base de datos mediante la función "find()". Como queremos crear una query específica, necesitamos crear un repositorio nuevo. Doctrine nos lo pone fácil, simplemente debemos actualizar los metadatos de nuestra clase Post:
Ya que hemos actualizado los metadatos debemos regenerar las entidades:
Doctrine creará dentro de "Entity" el fichero "PostRepository.php" donde podremos meter los métodos que queramos. El primero a incluir será el que recupere los últimos posts creados:
Por estar en la clase de repositorio, tenemos acceso directo a "createQueryBuilder()" sin necesidad de hacer uso del EntityManager. Además, no hace falta especificar el la entidad a usar (Post) mediante el método "from()", por estar en PostRepository, la cual está asociada a Post. Además, hemos añadido un parámetro $limit para limitar el número de resultados a devolver. Actualicemos ahora el controlador para que llame a esta nueva función:
Añadiendo comentarios a los posts
Dentro de un blog, los posts son la mitad del contenido. La otra mitad son los comentarios. A continuación veremos como hacer para añadirlos a nuestro proyecto.
Empezamos definiendo la entidad Comment. Para ello creamos un nuevo fichero Comment.php dentro de entidades con el siguiente código:
Veamos algunas cosas nuevas. En primer lugar hemos usado metadatos para vincular los comentarios a la entidad Post, mediante "ManyToOne". Además, indicamos que el enlace inverso del post a los comentarios estará disponible a través de "comments". Por lo tanto, debemos actualizar la entidad Post para añadir:
Además, Doctrine 2 necesita que inicialicemos la variable $comments a un objeto ArrayCollection, por lo que añadimos su uso a la cabecera:
... añadimos el constructor:
Actualizamos ahora las clases con:
...y una vez tenemos las clases actualizadas, forzamos la actualización de la base de datos para que se corresponda con la estructura creada.
Si da error. Borrar ambas tablas en el administrador de phpMyAdmin y probar de nuevo.
Mostrando los comentarios
Añadimos a CommentRepository la siguiente función:
Esta función nos devuelve los comentarios asociados a un post. Después actualizamos el controlador para que la acción show recupere los comentarios:
Como puede verse, empleamos la función que acabamos de crear en CommentRepository y luego pasamos la variable $comments a la plantilla.
La plantilla de comentarios
Si bien es cierto que podríamos mostrar los comentarios directamente en la plantilla show (show.html.twig), al ser una entidad aparte la de los comentarios, es más interesante crear una nueva plantilla por si queremos reutilizar dicha presentación. Por tanto, creamos "list.html.twig" (ojo a donde se pone esta vista):
Después añadimos en el bloque del body de la plantilla show.html.twig este código:
...y, en la misma plantilla (show.html.twig), para mejorar la presentación de los comentarios añadimos este bloque:
Este bloque recupera el bloque de hojas de estilos heredado e incluye el enlace a la hoja de estilos "blog.css" que contiene los estilos de los comentarios:
Añadiendo comentarios
En esta última parte aprenderemos a añadir comentarios a un post, mediante un formulario en la página show. Podríamos crear el formulario en el mismo controlador en vez de crear una nueva clase, pero al separar nos permite reutilizarlo en caso de necesidad. Creamos por tanto el formulario para la clase Comment mediante la consola de Symfony2:
Veamos el fichero creado en la carpeta "Form". La clase CommentType extiende de AbstractType, que introduce la clase FormBuilder, la mejor opción a la hora de crear formularios. Esta clase es capaz de simplificar la definición de los campos del formulario empleando los metadatos de cada elemento de la entidad Comment.
Mostrando el formulario de comentarios
Como queremos que el usuario añada nuevos comentarios desde la página show, podríamos crear el formulario en la acción show del controlador Blog y mostrarla directamente en la plantilla show. Una vez llegados a este punto, parece que queda claro que la mejor opción es separar dicho código para tenerlo mejor organizado y poder reutilizarlo. La única diferencia que hay entre mostrar los comentarios y añadir uno nuevo es que la acción de añadir un comentario nuevo necesita ser procesada, por lo que necesita un controlador. La forma de proceder por tanto será algo diferente. Veamos como hacerlo.
Rutas
Primero crearemos una nueva ruta en nuestro bundle que se encargue de dirigir el procesado del formulario a la acción "create" de un nuevo controlador "Comment":
Las cosas nuevas, los requisitos. Por un lado "_method" nos dice que solamente se acepte el método POST y por otro "post_id" nos dice que solo será válido un valor decimal para la variable.
El controlador
Ahora creamos el controlador que hemos referenciado en el fichero de rutas:
Creamos 2 acciones en el controlador. Por un lado tenemos "new", que se encargará de mostrar el formulario. Por otro lado tenemos la acción "create" que se encargará de procesar el envío del formulario.
Validación del formulario
Symfony2 nos proporciona unos validadores para facilitar la tarea de comprobación. Comencemos actualizando la entidad Comment añadiendo la siguiente función:
Añadimos además las nuevas sentencias "use":
Para definir los validadores debemos implementar el método estático loadValidatorMetadata. Éste proporciona un objeto ClassMetadata para meter restricciones a los miembros de la entidad. En este caso hacemos uso del validador "NotBlank" que simplemente devolverá true en caso de que el valor introducido en el formulario recibido sea vacío. Se pueden aplicar multitud de validadores: Listado de las opciones de validación
Ahora, al enviar el formulario, los datos pasarán por el método validador. En caso de no cumplir las restricciones, se mostrará el mensaje por defecto o, en nuestro caso, el mensaje que hemos definido al pasarle el parámetro al validador (array('message' => 'Debes indicar tu nombre'))).
Además, Symfony2 ofrece otro mensaje por defecto presentado con HTML5. Esta validación se hace en cliente (a diferencia de la anterior que se hace en el servidor) con lo que se evita el envío del formulario en caso de no cumplir los requisitos. Este mensaje por defecto también puede cambiarse.
La vista
A continuación creamos 2 plantillas para las acciones "new" y "create" del controlador. Comenzamos con new:
Plantilla muy simple que presenta el formulario y fija como acción del mismo la ruta que hemos marcado para acción "create" del controlador. Ahora añadimos la plantilla create:
Como la acción create del controlador Comment procesa el formulario, también necesita mostrarlo. Reutilizamos "form.html.twig" para mostrarlo y así prevenimos duplicación de código.
Ahora actualizamos la plantilla mostrar de Blog para añadir el formulario:
Si intentamos ver el resultado accediendo a un post, veremos que symfony nos lanza una excepción. Esta excepción nos dice que estamos intentando mostrar un campo "choice" en el formulario comment. Si vemos la plantilla del formulario Comment (form.html.twig), veremos el comando de Twig: {{ form_widget(form) }}. Dicho comando hace uso de FormBuilder para presentar el formulario entero sin indicarle nada, "adivinando" los campos en base a la entidad Comment. Si vamos a la entidad Comment, veremos la variable $post, que en realidad nos da la relación entre los comentarios y los posts. FormBuilder interpreta que esta variable debe presentarla como un "choice" field, pero no puede ofrecernos las opciones porque en realidad son objetos Post, no cadenas de texto. Podemos solucionar este problema implementando el método "__toString() en la entidad Post:
Ahora al recargar la página no dará error, pero como puede verse hay algunos campos que no necesitamos mostrar. Actualizamos la clase CommentType para mostrar solo los campos que necesitamos:
Si recargamos la página, veremos el resultado. Puede observarse que las etiquetas (labels) de los campos están en inglés. Por defecto symfony pone de label el nombre de la variable. ¿Cómo modificarlo?:
Le indicamos que añada el campo "user", el segundo parámetro indica el tipo de campo (dejándolo a null symfony pone el indicado por defecto) y el tercero nos permite pasar parámetros, entre los que se encuentra el valor de "label".
Para terminar con la visualización del formulario, añadimos las 2 siguientes líneas a la hoja de estilos del formulario (web/css/blog.css):
Ahora si mandamos el formulario y cumplimos los requisitos, tenemos que añadir la funcionalidad que guarde el comentario en la base de datos. Para ello, actualizamos la acción "create" del controlador Comment:
Ahora ya tenemos un blog con posts, a los que podemos añadir comentarios.
Y eso es todo amigos
Con esto ya hemos dado unos cuantos pasos en Symfony2. Hasta ahora hemos ido de la mano, pero ahora ya estamos listos para ir solos. Es un buen momento para ir pensando en el proyecto personal.